/*******************************************************************************
 * @file Copyright 2009 The CREATE Lab at CMU
 * 
 *       This file is part of JubJub, an open source project.
 *       http://code.google.com/p/jubjub/
 * 
 *       Redistribution and use in source and binary forms, with or without
 *       modification, are permitted provided that the following conditions are
 *       met:
 * 
 *       Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *       Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 *       Neither the name of Carnegie Mellon nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 *       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *       "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *       LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *       HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *       SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *       LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *       DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *       THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *       (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *       OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

package edu.cmu.ri.createlab.jj.blocks;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import edu.cmu.ri.createlab.jj.JubJub.EmptyStringException;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.AccessTypes;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.BlockTypes;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.VarTypes;
import edu.cmu.ri.createlab.jj.projects.JJClass;
import edu.cmu.ri.createlab.jj.visitors.BVReturn;
import edu.cmu.ri.createlab.jj.visitors.BlockVisitorInterface;

/**
 * 
 * @author Erik Pasternak
 * 
 * ObjectBlock is a representations of an object in code.
 * 
 * An ObjectBlock is a block representation of a class that is used
 * to define objects. Creating an ObjectBlock will also create a VarBlock
 * that should be maintained along with the Object. This lets the Object
 * be treated as both a variable and have its object attributes/methods
 * called with minimal specialized code. RefBlock blocks should reference
 * the VarBlock when using the object as a variable and the ObjectBlock
 * with the first element of params being a link to the RefBlock from
 * constructors, vars, or methods that the reference is calling.
 *
 */
public class ObjectBlock implements BlockInterface 
{
   /**
    * \defgroup BlockVars These variables are common to all BlockInterface types.
    */
   /*@{*/

   ///The name given to this block. CodeBlock and BlockBlock types should have unique names
   private String name;
   ///The JJClass which these blocks belong to and where they will be written to
   ///when output as code/xml
   private JJClass classOwner;
   ///What type of block this is from the list of BlockTypes
   private BlockTypes type;
   ///A unique ID for this block
   private int id;
   ///A list of RefBlock instances that reference this block.
   private List<RefBlock> refs;
   ///Whether this block is still valid or not
   private boolean valid;
   ///comment for this block
   private String comment;
   
   /*@}*/
   
   ///The class this block is an instance of
   private JJClass jjclass;
   ///The name of the object type. This lets a type be set without specifying a jjclass object
   private String objectType;
   
   ///Array size if this is being created as an array.
   private int length;
   
   /**
    * The VarBlock this object is linked to. When Objects are declared they get
    * created as a VarBlock and an ObjectBlock which are linked. This allows
    * RefBlock uses of the Object to treat them differently depending on how they
    * are used by referencing either the VarBlock or the ObjectBlock. When being
    * used as a variable (ex. "methodThatUsesObject(myObject);") the VarBlock
    * should be referenced. When methods or attributes of the object are being
    * used the ObjectBlock should be referenced with the first parameter being
    * which method/attribute to use.
    */
   private VarBlock variable;
   
   ///A list of references to constructors for this object
   private List<RefBlock> constructors;
   ///A list of references to vars for this object (the vars should belong to
   ///the class that defines this obect). The IDE should determine what's accessible by the user.
   private List<RefBlock> vars;
   ///A list of references to the non-static methods for this object. The IDE should determine
   ///when something is accessible by the user.
   private List<RefBlock> methods;
   
   /**
    * \defgroup CallableVars These variables are common to CodeBlock, BlockBlock, VarBlock, and ObjectBlock
    */
   /*@{*/
   
   ///Access keywords, same as in CodeBlock and BlockBlock classes, defined in BlockDefs
   private AccessTypes[] access;
   ///This is to provide easy boolean access to the access types for quick lookup.
   ///The order is PUBLIC, PRIVATE, PROTECTED, FINAL, STATIC and should be referenced
   ///by calling BlockDefs.AccessTypes.TYPE.getIndex
   private boolean[] isAccess = {false, false, false, false, false};
   ///The name used when calling/defining this block in code.
   private String callName;
   
   /*@}*/
   
   /**
    * @param name name of the object
    * @param owner the class it appears in
    * @param ID a unique id for it
    * @param IDV a unique id for its VarBlock representation
    * @param jjclass the class it's 
    */
   public ObjectBlock(String name, JJClass owner, int ID, int IDV, JJClass jjclass)
      throws Exception
   {
      setName(name);
      this.classOwner = owner;
      this.id = ID;
      this.type = BlockTypes.OBJECT;
      this.refs = new LinkedList<RefBlock>();
      this.length = 1;
      this.variable = new VarBlock(name, owner, IDV, VarTypes.OBJECT);
      this.variable.setObject(this);
      this.setAccess(null);
      this.constructors = new LinkedList<RefBlock>();
      this.vars = new LinkedList<RefBlock>();
      this.methods = new LinkedList<RefBlock>();
      this.setJJClass(jjclass);
      this.comment = "";
      //this.update();
      this.valid = true;
   }
   
   ///Scans through the class it's associated with and updates its constructors,
   ///vars, and methods, then updates all its refs.
   public void update()
   {
      if(this.jjclass != null)
      {
         this.setObjectType(this.jjclass.getCallName());
         //Scan the Class and update the object
         //Make sure to remove current refs first.
         List<BlockInterface> tempCons = jjclass.getConstructors();
         List<VarBlock> tempVars = jjclass.getVars();
         List<BlockInterface> tempMeth = jjclass.getMethodBlocks();
         
         ///Clear the vars references
         Iterator<RefBlock> it = this.vars.iterator();
         while(it.hasNext())
         {
            it.next().remove();
         }
         this.vars.clear();
         ///clear the constructors references
         it = this.constructors.iterator();
         while(it.hasNext())
         {
            it.next().remove();
         }
         this.constructors.clear();
         ///clear the methods references
         it = this.methods.iterator();
         while(it.hasNext())
         {
            it.next().remove();
         }
         this.methods.clear();
         ///add references back in
         int idCounter = 0;
         Iterator<VarBlock> vIt = tempVars.iterator();
         while(vIt.hasNext())
         {
            VarBlock tempVar = vIt.next();
            if(!tempVar.isStatic())
            {
               RefBlock tempRef = new RefBlock(idCounter++, tempVar);
               this.vars.add(tempRef);
            }
         }
         Iterator<BlockInterface> bIt = tempCons.iterator();
         while(bIt.hasNext())
         {
            BlockInterface tempBlock = bIt.next();
            if(tempBlock.getType() == BlockTypes.BLOCK)
            {
               if(!((BlockBlock)tempBlock).isStatic())
                  this.constructors.add(new RefBlock(idCounter++, tempBlock));
            }
            else if(tempBlock.getType() == BlockTypes.CODE)
            {
               if(!((CodeBlock)tempBlock).isStatic())
                  this.constructors.add(new RefBlock(idCounter++, tempBlock));
            }
         }
         bIt = tempMeth.iterator();
         while(bIt.hasNext())
         {
            BlockInterface tempBlock = bIt.next();
            if(tempBlock.getType() == BlockTypes.BLOCK)
            {
               if(!((BlockBlock)tempBlock).isStatic())
                  this.methods.add(new RefBlock(idCounter++, tempBlock));
            }
            else if(tempBlock.getType() == BlockTypes.CODE)
            {
               if(!((CodeBlock)tempBlock).isStatic())
                  this.methods.add(new RefBlock(idCounter++, tempBlock));
            }
         }
         setObjectType(this.jjclass.getCallName());
      }
            
      updateRefs();
   }
   
   /**
    * @param jjclass which class this is an instance of
    * 
    * This sets the class which this object is an instance of. Prior
    * to setting this it should be removed from the instance list of its
    * old class and added to the instance list of its new class.
    */
   public void setJJClass(JJClass jjclass)
   {
      //if(this.jjclass != null) this.jjclass.removeInstance(this);
      this.jjclass = jjclass;
      //jjclass.addInstance(this);
      this.update();
   }
   
   ///Returns the class this object is an instance of
   public JJClass getJJClass()
   {
      return jjclass;
   }
   
   public void setObjectType(String objectType)
   {
      this.objectType = objectType;
      this.variable.setObjectType(this.objectType);
   }

   public String getObjectType()
   {
      return objectType;
   }

   ///Returns the length of this object as a possible array.
   ///if the length is >1 it gets treated as an array.
   public int getLength()
   {
      return length;
   }
   
   ///Sets the size of a possible array for this object.
   ///if the length is >1 this is treated as an array.
   public void setLength(int length)
      throws BlockException
   {
      if(length < 1)
      {
         throw new BlockException("Cannot create array of length " + length);
      }
      this.length = length;
      this.variable.setLength(length);
   }
   
   
   
   ///returns the variable this block is linked to.
   public VarBlock getVariable()
   {
      return variable;
   }
   
   ///returns a list of the constructors for this object
   public List<RefBlock> getConstructors()
   {
      return constructors;
   }
   
   ///Returns whether it contains a specific constructor
   public boolean hasConstructor(RefBlock constructor)
   {
      return this.constructors.contains(constructor);
   }
   
   ///returns a list of the vars for this object
   public List<RefBlock> getVars()
   {
      return vars;
   }
   
   ///Returns whether it contains a specific variable
   public boolean hasVar(RefBlock var)
   {
      return this.vars.contains(var);
   }
   
   ///returns a list of the methods for this object
   public List<RefBlock> getMethods()
   {
      return methods;
   }
   
   ///Returns whether it contains a specific method
   public boolean hasMethod(RefBlock method)
   {
      return this.methods.contains(method);
   }
   
   ///adds a reference to a constructor Block
   public void addConstructor(RefBlock constructor)
   {
      constructors.add(constructor);
   }
   
   ///removes a reference to a constructor Block
   public void remConstructor(RefBlock constructor)
   {
      constructors.remove(constructor);
   }
   
   ///adds a reference to a var Block
   public void addVar(RefBlock var)
   {
      vars.add(var);
   }
   
   ///removes a reference to a var Block
   public void remVar(RefBlock var)
   {
      vars.remove(var);
   }
   
   ///adds a reference to a Method Block
   public void addMethod(RefBlock method)
   {
      methods.add(method);
   }
   
   ///removes a reference to a method Block
   public void remMethod(RefBlock method)
   {
      methods.remove(method);
   }
   
   /**
    * \defgroup CallableMethods These methods are common to CodeBlock, BlockBlock, VarBlock, and ObjectBlock.
    */
   /*@{*/
   
   ///Returns the list of access keywords
   public AccessTypes[] getAccess()
   {
      return access;
   }
   
   /**
    * @param access
    * @throws BlockException
    * 
    * Sets the list of AccessTypes keywords. May be null.
    * This will also check to ensure access types aren't being set for an INLINE
    * style block and that only one of public/private/protected is set. It will
    * also set the appropriate boolean flags for quick access to access settings.
    */
   public void setAccess(AccessTypes[] access) throws Exception
   {
      AccessTypes[] tempAccess = this.access;
      boolean[] tempIsAccess = this.isAccess.clone();
      ///Set the access keywords
      this.access = access;
      ///Set the boolean array to false to start
      for(int i = 0; i < this.isAccess.length; i++)
      {
         this.isAccess[i] = false;
      }
      ///Now set true any access keywords we added 
      if(access != null)
      {
         for(int i = 0; i < access.length; i++)
         {
            this.isAccess[access[i].getIndex()] = true;
         }
         ///You can only have one of the three visibility access types
         if((this.isAccess[AccessTypes.PUBLIC.getIndex()] && this.isAccess[AccessTypes.PRIVATE.getIndex()])
            || (this.isAccess[AccessTypes.PUBLIC.getIndex()] && this.isAccess[AccessTypes.PROTECTED.getIndex()])
            || (this.isAccess[AccessTypes.PRIVATE.getIndex()] && this.isAccess[AccessTypes.PROTECTED.getIndex()]))
         {
            ///so if we improperly set one of these undo changes and throw an exception
            this.access = tempAccess;
            this.isAccess = tempIsAccess;
            throw new BlockException("Can only have one of the following access types public/private/protected.");
         }
      }
      this.variable.setAccess(access);
      return;
   }
 
   ///returns whether or not this has the public keyword
   public boolean isPublic()
   {
      return isAccess[AccessTypes.PUBLIC.getIndex()];
   }

   ///returns whether or not this has the private keyword
   public boolean isPrivate()
   {
      return isAccess[AccessTypes.PRIVATE.getIndex()];
   }

   ///returns whether or not this has the protected keyword
   public boolean isProtected()
   {
      return isAccess[AccessTypes.PROTECTED.getIndex()];
   }

   ///returns whether or not this has the static keyword
   public boolean isStatic()
   {
      return isAccess[AccessTypes.STATIC.getIndex()];
   }

   ///returns whether or not this has the final keyword
   public boolean isFinal()
   {
      return isAccess[AccessTypes.FINAL.getIndex()];
   }
   
   ///returns the Java friendly name used to call this variable
   public String getCallName()
   {
      return callName;
   }
   
   /*@}*/

   /**
    * \defgroup BlockMethods These methods are common to all blocks.
    */
   /*@{*/

   ///Sets the comment for this block
   public void setComment(String comment)
   {
      this.comment = comment;
      //if(this.variable != null) this.variable.setComment(comment);
   }
   ///Gets the comment for this block
   public String getComment()
   {
      return comment;
   }
   
   ///Return whether this block is still valid or if it is deleted pending
   ///removal of all its refs.
   public boolean isValid()
   {
      return valid;
   }

   ///
   public boolean hasError()
   {
      if(this.jjclass == null) return true;
      return false;
   }
   
   ///Since it owns no refs it doesn't have anything to check for errors
   public void checkError()
   {
      if(hasError())
         if(this.variable.getOwner() != null)
            this.variable.getOwner().checkError();
   }
   
   ///This makes it easier for the garbage collector to remove this block
   ///by ensuring all the references it contains are removed.
   ///This should only be called through its VarBlock
   public void destroyBlock()
   {
      ///destroy our variable (commented out because we want to call this from
      ///the VarBlock).
      //this.variable.destroyBlock();
      ///Continue on with removing all references to everything
      //this.variable.setObject(null);
      //this.variable = null;
      Iterator<RefBlock> it;
      it = this.constructors.iterator();
      while(it.hasNext())
      {
         it.next().remove();
      }
      this.constructors.clear();
      it = this.vars.iterator();
      while(it.hasNext())
      {
         it.next().remove();
      }
      this.vars.clear();
      it = this.methods.iterator();
      while(it.hasNext())
      {
         it.next().remove();
      }
      this.methods.clear();
      //this.jjclass.removeInstance(this);   
      it = this.refs.iterator();
      while(it.hasNext())
      {
         it.next().clearBlock();
      }
      this.refs.clear();
   }
   
   ///Sets this block to not Valid and updates all its refs
   ///This should only be called by its corresponding VarBlock
   public void deleteBlock()
   {
      valid = false;
      //this.variable.deleteBlock();
      this.updateRefs();
   }
   
   ///Sets this block to Valid and updates all its refs
   ///This should only be called by its corresponding VarBlock
   public void undeleteBlock()
   {
      valid = true;
      //this.variable.undeleteBlock();
      this.updateRefs();
   }
   
   ///Accepts a BlockVisitorInterface and visits itself
   public BVReturn accept(BlockVisitorInterface bv)
   {
      bv.visit(this);
      return null;
   }
   
   ///returns the JJClass that owns this block
   
   public JJClass getOwner()
   {
      return classOwner;
   }

   ///returns the id for this block
   
   public int getID()
   {
      return id;
   }

   ///returns the name of this block
   
   public String getName()
   {
      return name;
   }

   ///returns the BlockTypes type of this block
   
   public BlockTypes getType()
   {
      return type;
   }
   
   ///returns the number of references to this block that exist
   public int getNumRefs()
   {
      return refs.size();
   }

   ///sets the JJClass that owns this block
   
   public void setOwner(JJClass owner)
   {
      this.classOwner = owner;
      
   }

   ///sets the name of this block as well as its callName.
   
   public void setName(String name) throws EmptyStringException
   {
      ///If the name is of 0 length we've made a mistake and need to throw
      ///an exception.
      if(name.length() == 0) 
      {
         throw new EmptyStringException("Empty string name");
      }
      this.name = name;
      ///The callname should be the same as the name with some changes to make it
      ///Java friendly
      this.callName = name.replaceAll("[^a-z&&[^A-Z]&&[^0-9]]+", "_");
      if(!(this.callName.substring(0, 1).matches("[a-zA-z]")))
      {
         this.callName = "a" + this.callName;
      }
   }
   
   ///Adds a RefBlock to the list of references
   
   public void addRef(RefBlock ref)
   {
      if(refs.contains(ref)) return;
      refs.add(ref);
   }

   ///Removes the given RefBlock from the list of references
   
   public void remRef(RefBlock ref)
   {
      refs.remove(ref);
   }

   ///Updates all the RefBlock references to this block
   
   public void updateRefs()
   {
      Iterator<RefBlock> it = refs.iterator();
      while(it.hasNext())
      {
         it.next().update();
      }
   }
   
   /*@}*/
   
   ///returns a readable string representation of the block
   public String toString()
   {
      String s = "";
      s += "Type:" + type;
      s += " ID:" + id;
      s += " Name:" + name;
      s += " CallName:" + callName;
      s += " Access:";
      if(access != null)
         for(int i = 0; i < access.length; i++)
            s += access[i] + " ";
      s += " Owner:" + classOwner;
      s += " NumRefs:" + refs.size();
      s += " NumConstructors:" + constructors.size();
      s += "\nVars:";
      if(vars != null)
         for(int i = 0; i < vars.size(); i++)
            s += vars.get(i).getName() + ", ";
      s += "\nMethods:";
      if(methods != null)
         for(int i = 0; i < methods.size(); i++)
            s += methods.get(i).getName() + ", ";
      return s;
   }
   
   ///Test method for ObjectBlock
   public static void main(String[] args)
   {
      
   }

}
